chore: add agentic Claude Code architecture and CI/CD scaffold#22
Merged
Conversation
Replaces the previous architecture-only CLAUDE.md with a session-front-door document: branch model, build/test commands, load-bearing constraints, secrets list, specialist routing table, and Definition of Done. The full orchestrator protocol lives at .claude/protocols/orchestrator.md; CLAUDE.md points there for non-trivial work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces the agentic Claude Code architecture:
- .claude/protocols/orchestrator.md
classify -> plan -> dispatch -> validate -> PR flow
- .claude/agents/*.md (six specialists)
swiftui-feature, data-model, services, build-ci, qa-tester,
git-workflow. Each has owned paths, off-limits paths, and rules.
- .claude/knowledge/*
architecture, signing-and-ci, testing, localization, firebase,
common-rules. gotchas.yaml seeded with three discovered invariants:
only Savely.xcscheme is shared, iOS 26 is the deployment floor,
and Config.plist / GoogleService-Info.plist must never be committed.
- .claude/commands/orch.md
/orch slash command that loads the orchestrator protocol.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds local-quality tooling that the CI workflow also runs:
- .swiftlint.yml
Curated rule set: bug-finders enabled (force_unwrapping, first_where,
contains_over_filter_count, etc.), style-noise disabled (line_length,
function_body_length). Custom rule warns on hardcoded user-facing
Text("...") literals in Views/.
- .githooks/pre-commit
Runs SwiftLint on staged .swift files before each commit. Skips
gracefully if SwiftLint is not installed.
- .githooks/commit-msg
Enforces Conventional Commits (feat, fix, refactor, chore, docs, ci,
build, test, style, perf) with scope and 72-char subject limits.
- scripts/install-hooks.sh
One-time activation: chmod +x .githooks/* and
git config core.hooksPath .githooks. Run after cloning.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First CI/CD pass for the project:
- .github/workflows/ci.yml
Triggers on PR (any base) and push to dev/main. Skips draft PRs.
Runner: macos-15. Steps: select Xcode -> cache SPM keyed on
Package.resolved hash -> SwiftLint --strict -> resolve packages ->
xcodebuild build -> xcodebuild test -> upload .xcresult on failure.
Uses xcbeautify for readable logs and CODE_SIGNING_ALLOWED=NO so
the runner does not need signing certs.
- .github/dependabot.yml
Weekly SPM bumps on Mondays (grouped patch+minor, individual majors)
and monthly GitHub Actions bumps. Both target the dev branch and
use Conventional Commit prefixes.
- .github/PULL_REQUEST_TEMPLATE.md
Summary / Why / Test plan / Risks / Checklist sections.
- .github/ISSUE_TEMPLATE/{bug_report,feature_request,config}
Two issue types and a config that disables blank issues.
The workflow's first run on this PR will likely need the Xcode version
or simulator name tweaked for whatever macos-15 ships — that is an
expected first task for build-ci-specialist.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The first CI run flagged 231 violations on the existing redesign code,
because --strict upgrades every warning to an error and the initial config
enabled style rules that don't catch bugs (comma spacing, vertical
whitespace, trailing closures in SwiftUI).
Two changes:
- .swiftlint.yml
Disable style rules that produce noise without finding bugs (comma,
colon, vertical_whitespace, trailing_newline, opening_brace,
statement_position, multiple_closures_with_trailing_closure,
legacy_objc_type, large_tuple, trailing_comma, implicit_optional_init,
static_over_final_class, etc.). Keep bug-finders: force_unwrapping,
first_where, contains_over_filter_count, identical_operands,
weak_delegate, sorted_first_last, etc. Move unused_declaration and
unused_import into analyzer_rules so SwiftLint stops complaining
they're misplaced.
- .github/workflows/ci.yml
Drop --strict. CI now fails only on error-severity violations
(force_cast, force_try). Warnings show up as inline annotations
via --reporter github-actions-logging.
force_unwrapping stays as warning so the ~10 existing force-unwraps
don't block merge today. After a dedicated cleanup PR, bump it to
error so new force-unwraps fail CI — that's the ratchet pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Config.plist is gitignored (it holds the OpenAI API key), but the Xcode
project declares it as a required build input. On a fresh checkout the
build fails before compilation:
error: Build input file cannot be found:
'.../Savely/Config.plist' (in target 'Savely' from project 'Savely')
CI never calls the OpenAI API, so the real key is not needed — only the
file. Add a step that writes a minimal plist with the OPENAI_API_KEY
field set to a placeholder value before the SPM resolve step.
Also append the rule to .claude/knowledge/gotchas.yaml so future Claude
sessions don't have to rediscover this. Same pattern will apply to
GoogleService-Info.plist once that PR (chore/stop-tracking-firebase-config)
removes it from the index.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CI test step failed with:
compiling for iOS 17.5, but module 'Savely' has a minimum deployment
target of iOS 26.0
@testable import Savely
The Savely app target was bumped to iOS 26 during the redesign, but the
project default stayed at 17.0 and SavelyTests stayed at 17.5. The test
bundle was compiling against 17.5 while @testable importing a 26.0 module
— a guaranteed link failure.
Surgical fix in project.pbxproj — six IPHONEOS_DEPLOYMENT_TARGET values:
project Debug 17.0 -> 26.0
project Release 17.0 -> 26.0
Savely Debug 26.0 (already)
Savely Release 26.0 (already)
SavelyTests Debug 17.5 -> 26.0
SavelyTests Release 17.5 -> 26.0
SavelyUITests has no per-target override and inherits the project default,
which is now 26.0.
Updates the gotcha entry in .claude/knowledge/gotchas.yaml to reflect the
resolved state and to add the lesson: when bumping a project default, also
bump every per-target override.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…urn 10min
Tests passed in 10 minutes, but most of that time was the Xcode template's
auto-generated launch performance tests:
SavelyUITestsLaunchTests::testLaunch (×8 iterations: 32s, 30s, 82s, 38s, 18s, 14s, 10s, 10s)
SavelyUITests::testLaunchPerformance (210s)
SavelyUITests::testExample (112s)
XCApplicationLaunchMetric runs the app multiple times measuring start-up
time, then fails if any iteration falls outside the std-dev threshold.
On shared CI runners one slow iteration is normal — and that's exactly
what flagged Process completed with exit code 65 here (one launch took
81.7s, blew the threshold).
Net signal: zero. The template tests assert nothing about app behavior —
they only measure how fast it launches, which is meaningless on a
contended runner. Burning ~10 min/run for that is a bad trade.
Add -skip-testing:SavelyUITests to the Test step. The Build step still
compiles the bundle so we catch breakage, just doesn't execute tests.
SavelyTests (the unit-test bundle, currently empty) still runs.
Document the rationale in .claude/knowledge/gotchas.yaml so qa-tester
knows to drop the flag once real UI tests exist.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
.claude/with an orchestrator protocol that classifies, plans, dispatches, validates, and opens PRs.CLAUDE.mdas a session front door: branch model, build/test commands, load-bearing constraints, secrets list, specialist routing..swiftlint.yml, pre-commit lint hook, commit-msg Conventional Commits hook, one-time install script.Why
This codifies the workflow rules we agreed on (one PR at a time, options-over-decisions, no auto-merge,
devas integration branch,mainas release-only) so they survive across Claude sessions instead of being re-derived each time. It also gets a CI gate on every PR before the codebase grows further.Test plan
ci.ymlmay need a one-line tweak formacos-15's actual installed Xcode; that isbuild-ci-specialist's first follow-up)../scripts/install-hooks.shactivates hooks;brew install swiftlintenables the pre-commit lint check.devandmainset via GitHub UI (Settings → Branches → require PR + status check + linear history).Risks
26.0) and simulator name (iPhone 16 Pro) are guesses for whatmacos-15runners ship today. Easy fix once we see the failure.*.xcschemeis gitignored globally. Pre-existing rule, unchanged here. Will silently block any new shared scheme; flagged for follow-up ingotchasdiscussions.Savely/GoogleService-Info.plistremains tracked in history. Now in.gitignore(added onmainwith the redesign), so future changes won't be committed. Separatechore/stop-tracking-firebase-configPR willgit rm --cachedit. Repo is private and Firebase iOS API keys are not true secrets, so this is hygiene, not an emergency.Known follow-up tasks (queued for
build-ci-specialist)ContentView.swift(one inSavely/, one inSavely/Views/).*.xcschemegitignore rule.SavelyTests/SavelyUITestsschemes shared if needed.Checklist
chore/)dev, notmain🤖 Generated with Claude Code